package com.diy.sigmund.diyuser.test.controller;

import com.diy.sigmund.diycommon.entity.ResultMsg;
import com.diy.sigmund.diycommon.entity.base.exception.DiyRuntimeException;
import com.diy.sigmund.diycommon.entity.constant.LockConstant;
import com.diy.sigmund.diycommon.entity.enumerate.ResponseEnum;
import com.diy.sigmund.diycommon.entity.po.user.UserBase;
import com.diy.sigmund.diycommon.middleware.cache.redis.RedisDistributedLock;
import com.diy.sigmund.diycommon.spring.CustomApplicationContext;
import com.diy.sigmund.diycommon.test.entity.RedissonDTO;
import com.diy.sigmund.diycommon.util.json.JacksonUtil;
import com.diy.sigmund.diycommon.util.seq.SeqUtil;
import com.diy.sigmund.diyuser.mapper.TestSqlMapper;
import com.diy.sigmund.diyuser.mapper.UserBaseMapper;
import com.diy.sigmund.diyuser.mapper.service.impl.TestSqlServiceImpl;
import java.util.Date;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author ylm-sigmund
 * @since 2021/8/27 9:41
 */
@RestController
@RequestMapping("/user/transactionalTestController")
public class TransactionalTestController {

    private static final Logger log = LoggerFactory.getLogger(TransactionalTestController.class);

    @Autowired
    private UserBaseMapper userBaseMapper;
    @Autowired
    private TestSqlMapper testSqlMapper;
    @Autowired
    private TestSqlServiceImpl testSqlService;
    @Autowired
    private RedisDistributedLock redisDistributedLock;
    // @Autowired
    // private RedissonTransactionManager customRedissonTxManager;

    /**
     * <br>TransactionDefinition.PROPAGATION_REQUIRED：如果当前存在事务，则加入该事务；如果当前没有事务，则创建一个新的事务。这是默认值。
     * <br>TransactionDefinition.PROPAGATION_REQUIRES_NEW：创建一个新的事务，如果当前存在事务，则把当前事务挂起。
     * <br>TransactionDefinition.PROPAGATION_SUPPORTS：如果当前存在事务，则加入该事务；如果当前没有事务，则以非事务的方式继续运行。
     * <br>TransactionDefinition.PROPAGATION_NOT_SUPPORTED：以非事务方式运行，如果当前存在事务，则把当前事务挂起。
     * <br>TransactionDefinition.PROPAGATION_NEVER：以非事务方式运行，如果当前存在事务，则抛出异常。
     * <br>TransactionDefinition.PROPAGATION_MANDATORY：如果当前存在事务，则加入该事务；如果当前没有事务，则抛出异常。
     * <br>TransactionDefinition.PROPAGATION_NESTED：如果当前存在事务，则创建一个事务作为当前事务的嵌套事务来运行；如果当前没有事务，则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
     *
     * @return ResultMsg
     */
    @PostMapping("/isActualTransactionActive")
    public ResultMsg isActualTransactionActive() {
        String requestId = SeqUtil.getRequestId();
        try {
            redisDistributedLock.tryLock(LockConstant.TEST_LOCK, requestId, 1000);
            CustomApplicationContext.getBean(TransactionalTestController.class).handle();
        } finally {
            redisDistributedLock.releaseLock(LockConstant.TEST_LOCK, requestId);
        }
        return ResultMsg.success(null);
    }

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, timeout = 30, readOnly = false, rollbackFor = {
            Exception.class})
    // @Transactional(noRollbackFor = ArithmeticException.class, rollbackFor = ArithmeticException.class)
    public void handle() {
        // 返回true: 该方法中存在事务，false则不存在
        log.info("该方法中存在事务={},getCurrentTransactionName={},getCurrentTransactionIsolationLevel={}",
                TransactionSynchronizationManager.isActualTransactionActive(),
                TransactionSynchronizationManager.getCurrentTransactionName(),
                TransactionSynchronizationManager.getCurrentTransactionIsolationLevel());

        UserBase userBase = new UserBase();
        UserBase userBase1 = userBaseMapper.selectByPrimaryKey("1");
        // int a = 1/0;
        userBaseMapper.deleteByPrimaryKey("1112");
        userBase.setUserId("1112");
        userBase.setRegisterSource("1");
        userBase.setCreateTime(new Date());
        userBase.setUpdateTime(new Date());
        int i = userBaseMapper.insertSelective(userBase);
        if (i > 0) {
            // throw new DiyRuntimeException(ResponseEnum.REQUEST_FREQUENT);
        }
    }

    /**
     * 事务管理器互斥，spring-boot 默认使用的是DataSource事务管理器，Redisson事务管理器需要手动配置，但是与默认DataSource事务管理器互斥的
     */
    @PostMapping("/dbAndCacheTest")
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, timeout = 3000, readOnly = false, rollbackFor = {
            Exception.class})
    public ResultMsg dbAndCacheTest(@RequestBody RedissonDTO redissonDTO) {
        log.info("该方法中存在事务={},getCurrentTransactionName={},getCurrentTransactionIsolationLevel={}",
                TransactionSynchronizationManager.isActualTransactionActive(),
                TransactionSynchronizationManager.getCurrentTransactionName(),
                TransactionSynchronizationManager.getCurrentTransactionIsolationLevel());
        UserBase userBase = new UserBase();
        userBase.setUserId("1112");
        userBase.setRegisterSource("1");
        userBase.setCreateTime(new Date());
        userBase.setUpdateTime(new Date());
        ResultMsg fail = ResultMsg.fail(ResponseEnum.ILLEGAL_PRODUCT_ID);
        boolean success = fail.hasSuccess();
        int i = userBaseMapper.insertSelective(userBase);
        // RTransaction transaction = customRedissonTxManager.getCurrentTransaction();
        // RMap<String, String> map = transaction.getMap("test1");
        // map.put("1", "2");
        log.info("redissonTransactionManager");
        if (Objects.equals("aa", redissonDTO.getName())) {
            // 抛异常可以回滚
            throw new DiyRuntimeException();
        }
        return ResultMsg.success();
    }

    /**
     * 事务未提交依然可以查询到在事务中未提交的数据。
     */
    @PostMapping("/transactionUnCommitThenSelect")
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, timeout = 3000, readOnly = false, rollbackFor = {
            Exception.class})
    public ResultMsg transactionUnCommitThenSelect(@RequestBody RedissonDTO redissonDTO) {
        // 未使用注解时，该方法中存在事务=false,getCurrentTransactionName=null,getCurrentTransactionIsolationLevel=null
        // 使用注解时，该方法中存在事务=true,getCurrentTransactionName=com.diy.sigmund.diyuser.test.controller.TransactionalTestController.transactionUnCommitThenSelect,getCurrentTransactionIsolationLevel=2
        log.info("该方法中存在事务={},getCurrentTransactionName={},getCurrentTransactionIsolationLevel={}",
                TransactionSynchronizationManager.isActualTransactionActive(),
                TransactionSynchronizationManager.getCurrentTransactionName(),
                TransactionSynchronizationManager.getCurrentTransactionIsolationLevel());
        UserBase userBase = new UserBase();
        userBase.setUserId("1112");
        userBase.setRegisterSource("1");
        userBase.setCreateTime(new Date());
        userBase.setUpdateTime(new Date());
        ResultMsg fail = ResultMsg.fail(ResponseEnum.ILLEGAL_PRODUCT_ID);
        boolean success = fail.hasSuccess();
        int i = userBaseMapper.insertSelective(userBase);
        UserBase userBase1 = userBaseMapper.selectByPrimaryKey("1112");
        log.info("selectByPrimaryKey:{}", JacksonUtil.toJson(userBase1));
        return ResultMsg.success();
    }

    /**
     * <br>抛出的异常是DiyRuntimeException，距RuntimeException的深度是1，距Exception的深度是2
     * <br>当rollbackFor = RuntimeException，noRollbackFor = RuntimeException，事务会回滚，因为后者的深度depth 1 = deepest 1，winner=RollbackRuleAttribute
     * <br>当rollbackFor = Exception，noRollbackFor = RuntimeException，事务不会回滚，因为后者的深度depth 1 < deepest 2，winner=NoRollbackRuleAttribute
     * <br>当rollbackFor = RuntimeException，noRollbackFor = Exception，事务会回滚，因为后者的深度depth 2 > deepest 1，winner=RollbackRuleAttribute
     * <br>结论：the closest in the inheritance hierarchy to the exception
     * <br>继承层次结构中最接近异常的
     * <br>最接近抛出的异常类深度对应的RollbackRuleAttribute类决定是否会回滚，当深度相同时，且rollbackFor优先级高于noRollbackFor属性
     * <br>
     * <br>当属性设置为readOnly = true，且事务方法是增删改时，会抛出异常：Connection is read-only. Queries leading to data modification are not allowed
     * <br>at com.mysql.cj.jdbc.ClientPreparedStatement.execute(ClientPreparedStatement.java:318)
     *
     * @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn(java.lang.Throwable)
     */
    @PostMapping("/testNested")
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, timeout = 3000, readOnly = false, rollbackFor = {
            RuntimeException.class}, noRollbackFor = Exception.class)
    public ResultMsg testNested() {
        UserBase userBase = new UserBase();
        userBase.setUserId("1113");
        userBase.setRegisterSource("1");
        int i = userBaseMapper.insertSelective(userBase);
        // 嵌套事务，默认的配置，子事务回滚，父事务也跟着回滚了，回滚点之前的内容也回滚，若手动处理子事务的异常，则不会影响回滚点之前的事务
        // 父事务回滚，子事务也会跟着回滚
        // 子事务是父事务的一部分，会由父事务统一提交。如果外部事务commit，子事务也会commit，roll bak也一样
        // org.springframework.transaction.support.AbstractPlatformTransactionManager.setNestedTransactionAllowed
        // org.springframework.jdbc.datasource.ConnectionHolder.supportsSavepoints
        try {
            // CustomApplicationContext.getBean(TransactionalTestController.class).test();
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 创建新事务，默认的配置，子事务回滚，父事务也跟着回滚了
        // 父事务回滚，子事务不会跟着回滚
        // 完全的新事务，与外部事务相互独立
        // CustomApplicationContext.getBean(TransactionalTestController.class).test1();
        UserBase userBase1 = new UserBase();
        userBase1.setUserId("1115");
        userBase1.setRegisterSource("1");
        int i1 = userBaseMapper.insertSelective(userBase1);
        if (i1 > 0) {
            // throw new DiyRuntimeException(ResponseEnum.REQUEST_FAIL);
        }
        return ResultMsg.success();
    }

    @Transactional(propagation = Propagation.NESTED, isolation = Isolation.READ_COMMITTED, timeout = 3000, readOnly = false, rollbackFor = {
            Exception.class, RuntimeException.class})
    public void test() {
        UserBase userBase = new UserBase();
        userBase.setUserId("1114");
        userBase.setRegisterSource("1");
        int i = userBaseMapper.insertSelective(userBase);
        if (i > 0) {
            throw new DiyRuntimeException(ResponseEnum.REQUEST_FAIL);
        }
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED, timeout = 3000, readOnly = false, rollbackFor = {
            Exception.class, RuntimeException.class})
    public void test1() {
        UserBase userBase = new UserBase();
        userBase.setUserId("1114");
        userBase.setRegisterSource("1");
        int i = userBaseMapper.insertSelective(userBase);
        if (i > 0) {
            // throw new DiyRuntimeException(ResponseEnum.REQUEST_FAIL);
        }
    }

    /**
     * 测试点:
     * <br>事务 通过
     * <br>超时配置timeout
     * <br>原本的分页pageHelper 通过
     * <br>mp的分页 通过
     * <br>日志打印 通过
     * <br>自定义不打印日志 通过
     */
    @PostMapping("/testMyBatis-plus/Transactional")
    @Transactional(transactionManager = "transactionManager", propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, timeout = 3000, readOnly = false, rollbackFor = {
            Exception.class})
    public ResultMsg test2() {
        UserBase userBase = new UserBase();
        userBase.setUserId("1430147148544081921");
        userBase.setUpdateTime(new Date());
        int i = userBaseMapper.updateByPrimaryKeySelective(userBase);

        // TestSql testSql = new TestSql();
        // testSql.setId(SeqUtil.snowflakeId());
        // testSqlService.saveOrUpdate(testSql);
        // testSql.setId(SeqUtil.snowflakeId());
        // testSqlMapper.insert(testSql);

        // Page<TestSql> of = PageDTO.of(1, 5);
        // IPage<TestSql> page = testSqlMapper.selectPage(of, null);
        // log.info("事务方法testTestSqlSelect-testSql:{}", JacksonUtil.toJson(page));
        if (i > 0) {
            throw new DiyRuntimeException(ResponseEnum.REQUEST_FAIL);
        }
        return ResultMsg.success();
    }
}
